import os
import re
import time
import zipfile
from subprocess import Popen, PIPE
from threading import Thread
from data import userData


class exeControl:
    # 初始化
    def __init__(self, dls_path, iniData: userData):
        # 信号初始化
        self.__server_exe = Popen
        self.control_close = False
        self.exe_alive = False
        self.__thread_list = []
        self.eventLog = []
        self.control_path = dls_path
        # 数据参数
        self.ini = iniData.iniCls

    # 启动服务器的实现
    def __exeStartWay(self):
        try:
            # 回到初始工作目录
            os.chdir(self.control_path)
            # 分割路径，获得目录为空时不切换目录
            exe_dir, exe_name = os.path.split(self.ini.exe_cmd)
            if exe_dir:
                os.chdir(exe_dir)
            # 启动程序
            self.__server_exe = Popen(exe_name, stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=False)
        except Exception as err:
            self.doRegPrint(f"[DLS][ERROR][EXE] 程序启动失败，请检查启动命令再运行 原因: {err}")
            return
        # 成功启动
        self.exe_alive = True
        self.doRegPrint("[DLS][INFO][EXE] 服务器开始运行")
        # 循环判断是否结束线程
        while True:
            time.sleep(1)
            if not self.exe_alive or self.control_close:
                try:
                    # 尝试正常关闭
                    self.__server_exe.stdin.write("stop\n".encode())
                    self.__server_exe.stdin.flush()
                    self.__server_exe.wait(6)
                except:
                    # 强制关闭程序
                    self.__server_exe.kill()
                    self.__server_exe.wait()
                self.doRegPrint("[DLS][INFO][EXE] 服务器停止运行")
                break

    # 辅助线程：重定向输出
    def __output(self):
        while not self.control_close:
            time.sleep(0.01)
            if self.exe_alive:
                try:
                    # 注意：readLine阻塞进程
                    next_line = self.__server_exe.stdout.readline().decode().strip("\n").strip("\r")
                    # 判断输出执行结果
                    if next_line != "":
                        # 匹配消息返回到群
                        self.doRegPrint(next_line)
                    # 若程序终止打印完剩余的输出
                    while not self.exe_alive:
                        next_line = self.__server_exe.stdout.readline().decode("utf-8").strip("\n").strip("\r")
                        if next_line != "":
                            # 匹配消息返回到群
                            self.doRegPrint(next_line)
                        else:
                            break
                except Exception as err:
                    self.doRegPrint(f"[DLS][ERROR][INP] 错误的输出编码: {err}")

    # 辅助线程：判断运行状况
    def __ifExit(self):
        while not self.control_close:
            time.sleep(1)
            if self.exe_alive:
                if self.__server_exe.poll() is not None:
                    self.exe_alive = False
                    # 判断是否需要重启
                    if self.__server_exe.poll() == 1:
                        if self.ini.exe_ReAlive:
                            self.doRegPrint("\n[DLS][WARN][EXE] 进程崩溃，将在5秒后重启\n")
                            time.sleep(5)
                            self.exeStart()
                        else:
                            self.doRegPrint("\n[DLS][WARN][EXE] 进程崩溃，未设置重启\n")

    # 辅助线程：时钟(定时任务)
    def __clock(self):
        while not self.control_close:
            time.sleep(1)
            if self.ini.exe_Bak:
                now_day = time.strftime("%a", time.localtime())
                now_time = time.strftime("%H%M%S", time.localtime())
                if now_day in self.ini.re_bak_day:
                    if now_time in self.ini.re_bak_time:
                        self.bakWorld()

    # 重定向输入传入
    def executeCmd(self, command):
        byteCommand = (command + "\n").encode()
        if self.exe_alive:
            try:
                # 命令执行
                self.__server_exe.stdin.write(byteCommand)
                self.__server_exe.stdin.flush()
                return True
            except Exception as err:
                self.doRegPrint(f"[DLS][WARN][CMD] 执行失败: {err}")
        elif command == "start":
            self.exeStart()
        else:
            self.doRegPrint("[DLS][INFO][CMD] 服务器未开启！")

    # 备份存档的实现
    def bakWorld(self):
        # 关服备份
        self.exeStop()
        self.doRegPrint("\n======>开始备份<======")
        self.doRegPrint("[DLS][INFO][BAK] 服务器开始备份")
        # 设定压缩目录与压缩包名
        bak_time = time.strftime(self.ini.bak_name, time.localtime())
        zip_name = f"{self.ini.bak_dir}{bak_time}.zip"
        dir_path = self.ini.world_dir
        # 开始压缩进程
        fZip = zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED)
        for path, dir_names, filenames in os.walk(dir_path):
            f_path = path.replace(dir_path, '')
            for filename in filenames:
                fZip.write(os.path.join(path, filename), os.path.join(f_path, filename))
        # 输出结果信息
        file_size = format(os.path.getsize(zip_name) / 1000, '.2f')
        self.doRegPrint(f"[DLS][INFO][BAK] 备份保存至: {zip_name}")
        self.doRegPrint(f"[DLS][INFO][BAK] 服务器备份完毕，存档大小 {file_size}KB")
        # 备份完毕开服
        self.doRegPrint("======>备份完毕<======\n")
        self.exeStart()

    # 线程生成与管理
    def __myThread(self, func):
        th = Thread(target=func, daemon=True)
        th.start()
        self.__thread_list.append(th)

    # 单独启动服务器
    def exeStart(self):
        if not self.exe_alive:
            self.__myThread(self.__exeStartWay)
        else:
            self.doRegPrint("[DLS][INFO][CMD] 服务器已开启!")

    # 单独关闭服务器
    def exeStop(self):
        if not self.exe_alive:
            self.doRegPrint("[DLS][INFO][CMD] 服务器已关闭!")
        else:
            self.exe_alive = False
            self.__server_exe.wait()

    # 等待线程运行
    def wait(self):
        for th in self.__thread_list:
            th.join()
        # 清空线程列表
        self.__thread_list = []

    # 启动服务器包括监听器
    def startAll(self):
        self.control_close = False
        self.exeStart()
        self.__myThread(self.__output)
        self.__myThread(self.__ifExit)
        self.__myThread(self.__clock)

    # 关闭服务器包括监听器
    def stopAll(self):
        self.control_close = True
        self.wait()

    # 重新加载配置
    def iniReload(self, iniData: userData):
        self.ini = iniData.iniCls

    # 增加发送群消息至事件列表
    def addGroupSend(self, msg):
        if self.ini.robot_on:
            self.eventLog.append(["sendGroup", msg])

    # 匹配消息加入消息事件
    def doRegPrint(self, next_line):
        # 首先打印输出
        print(next_line)
        # 若未开启机器人则函数结束
        if not self.ini.robot_on:
            return
        # 转换彩色输出
        next_line = self.dealColorLog(next_line)
        # 用户设定的匹配
        for reg in self.ini.robot_reList:
            try:
                res = re.findall(reg["正则"], next_line)
                if len(res) == 1:
                    result = res[0]
                    msg = reg["返回消息格式"]
                    # 单次与多次匹配处理
                    if type(result) == str:
                        msg = msg.replace("%p", next_line).replace("%s", result)
                    elif type(result) == tuple:
                        msg = msg.replace("%p", next_line)
                        for i in range(len(result)):
                            msg = msg.replace(f"%s{i+1}", result[i])
                        msg = msg.replace("%s", result[0])
                    # 确认追加输出行数
                    if type(reg.get("输出追加行数")) == int:
                        if reg["输出追加行数"] > 0:
                            for i in range(reg["输出追加行数"]):
                                pla = self.__server_exe.stdout.readline().decode().strip("\n").strip("\r")
                                print(pla)
                                # 转换彩色输出
                                pla = self.dealColorLog(pla)
                                # 剔除前缀
                                re_pla = re.search("(?<=\\[Server]).*", pla)
                                if re_pla:
                                    pla = re_pla.group().strip()
                                # 不为空字符串时才追加输出
                                if pla:
                                    msg = msg + "\n" + pla
                    # 发送至群消息
                    self.addGroupSend(msg)
            except Exception as err:
                self.doRegPrint(f"[DLS][WARN][REG] 错误的匹配: {reg} 原因: {err}")

    # 处理彩色输出
    @staticmethod
    def dealColorLog(content):
        if re.search("\033\\[.+?m", content):
            content = re.sub("\033\\[.+?m", "", content)
        return content
